1 /** 2 Copyright: Copyright (c) 2019, Joakim Brännström. All rights reserved. 3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This module contains an analyser and runner for 7 [iwyu](https://github.com/include-what-you-use/include-what-you-use). 8 */ 9 module code_checker.engine.builtin.include_what_you_use; 10 11 import logger = std.experimental.logger; 12 import std.concurrency : Tid, thisTid; 13 import std.exception : collectException; 14 import std.typecons : Tuple; 15 16 import my.path : AbsolutePath, Path; 17 18 import code_checker.engine.builtin.clang_tidy_classification : CountErrorsResult; 19 import code_checker.engine.file_filter; 20 import code_checker.engine.types; 21 import code_checker.process : RunResult; 22 23 @safe: 24 25 class IncludeWhatYouUse : BaseFixture { 26 private { 27 Environment env; 28 Result result_; 29 string[] iwyuArgs; 30 } 31 32 override string name() { 33 return "iwyu"; 34 } 35 36 override string explain() { 37 return "using iwyu (include what you use)"; 38 } 39 40 /// The environment the analyzers execute in. 41 override void putEnv(Environment v) { 42 this.env = v; 43 } 44 45 /// Setup the environment for analyze. 46 override void setup() { 47 import std.algorithm : copy, map, joiner; 48 import std.array : appender; 49 import std.file : exists; 50 import std.range : put, only; 51 import code_checker.utility : replaceConfigWords, warnIfFileDoNotExist; 52 53 auto app = appender!(string[])(); 54 app.put(env.conf.iwyu.binary); 55 only(env.conf.iwyu.maps, env.conf.iwyu.defaultMaps).joiner.replaceConfigWords.warnIfFileDoNotExist.map!( 56 a => only("-Xiwyu", "--mapping_file=" ~ a)).joiner.copy(app); 57 env.conf.iwyu.extraFlags.copy(app); 58 iwyuArgs = app.data; 59 } 60 61 override void execute() { 62 result_.status = Status.passed; 63 executeParallel(env, iwyuArgs, result_); 64 } 65 66 override void tearDown() { 67 } 68 69 override Result result() { 70 return result_; 71 } 72 } 73 74 private: 75 76 void executeParallel(Environment env, string[] iwyuArgs, ref Result result_) @safe { 77 import std.algorithm : copy, map, joiner; 78 import std.array : appender, array; 79 import std.concurrency : thisTid, receiveTimeout; 80 import std.file : exists; 81 import std.format : format; 82 import std.parallelism : task, TaskPool; 83 import code_checker.engine.compile_db; 84 import code_checker.engine.logger : Logger; 85 86 bool logged_failure; 87 auto logg = Logger(env.conf.logg.dir); 88 89 void collectResult(immutable(IwyuResult)* res_) @trusted nothrow { 90 import std.typecons : nullableRef; 91 import colorlog; 92 93 auto res = nullableRef(cast() res_); 94 95 logger.infof("%s '%s'", "iwyu analyzing".color(Color.yellow) 96 .bg(Background.black), res.file).collectException; 97 98 // seems like 2 also means OK. 99 const allIsOk = res.exitStatus == 0 || res.exitStatus == 2; 100 101 if (!allIsOk) 102 result_.score -= res.exitStatus > 0 ? res.exitStatus : 0; 103 104 if (!allIsOk) { 105 result_.failed ~= res.file; 106 res.print; 107 108 if (env.conf.logg.toFile) { 109 try { 110 const logFile = Path(res.file.toString ~ ".iwyu").AbsolutePath; 111 logg.put(logFile, [res.output]); 112 } catch (Exception e) { 113 logger.warning(e.msg).collectException; 114 logger.warning("Unable to log to file").collectException; 115 } 116 } 117 118 if (!logged_failure) { 119 result_.msg ~= Msg(MsgSeverity.failReason, "iwyu suggested improvements"); 120 logged_failure = true; 121 } 122 123 try { 124 result_.msg ~= Msg(MsgSeverity.improveSuggestion, 125 format("iwyu: %s in %s", res.exitStatus, res.file)); 126 } catch (Exception e) { 127 logger.warning(e.msg).collectException; 128 logger.warning("Unable to add user message to the result").collectException; 129 } 130 131 result_.status = mergeStatus(result_.status, Status.failed); 132 } 133 } 134 135 auto pool = new TaskPool; 136 scope (exit) 137 pool.finish; 138 ExpectedReplyCounter cond; 139 140 auto file_filter = FileFilter(env.conf.staticCode.fileExcludeFilter); 141 auto fixedDb = toRange(env); 142 143 foreach (cmd; fixedDb) { 144 if (!exists(cmd.cmd.absoluteFile.toString)) { 145 result_.score -= 1000; 146 result_.msg ~= Msg(MsgSeverity.failReason, "iwyu where unable to find one of the specified files in compile_commands.json on the filesystem. Your compile_commands.json is probably out of sync. Regenerate it."); 147 continue; 148 } else if (!file_filter.match(cmd.cmd.absoluteFile)) { 149 continue; 150 } 151 152 cond.expected++; 153 154 immutable(IwyuWork)* w = () @trusted { 155 auto args = appender!(string[])(); 156 iwyuArgs.copy(args); 157 cmd.flags.systemIncludes.map!(a => ["-isystem", a]).joiner.copy(args); 158 cmd.flags.includes.map!(a => ["-I", a]).joiner.copy(args); 159 args.put(cmd.cmd.absoluteFile); 160 161 return cast(immutable) new IwyuWork(args.data, cmd.cmd.absoluteFile); 162 }(); 163 164 auto t = task!taskIwyu(thisTid, w); 165 pool.put(t); 166 } 167 168 while (cond.isWaitingForReplies) { 169 import core.time : dur; 170 171 () @trusted { 172 try { 173 if (receiveTimeout(1.dur!"seconds", &collectResult)) { 174 cond.replies++; 175 } 176 } catch (Exception e) { 177 logger.error(e.msg); 178 } 179 }(); 180 } 181 } 182 183 struct ExpectedReplyCounter { 184 int expected; 185 int replies; 186 187 bool isWaitingForReplies() { 188 return replies < expected; 189 } 190 } 191 192 struct IwyuResult { 193 AbsolutePath file; 194 195 /// Exit status from running iwyu. 196 int exitStatus; 197 /// Captured output from iwyu. 198 string[] output; 199 200 void print() @safe nothrow const scope { 201 import std.stdio : writeln; 202 203 foreach (l; output) 204 writeln(l).collectException; 205 } 206 } 207 208 struct IwyuWork { 209 string[] args; 210 AbsolutePath file; 211 } 212 213 void taskIwyu(Tid owner, immutable IwyuWork* work_) nothrow @trusted { 214 import std.concurrency : send; 215 import code_checker.process; 216 217 IwyuWork* work = cast(IwyuWork*) work_; 218 auto rval = new IwyuResult; 219 rval.file = work.file; 220 221 try { 222 auto res = run(work.args); 223 rval.exitStatus = res.status; 224 rval.output = res.stdout ~ res.stderr; 225 } catch (Exception e) { 226 logger.warning(e.msg).collectException; 227 } 228 229 while (true) { 230 try { 231 owner.send(cast(immutable) rval); 232 break; 233 } catch (Exception e) { 234 logger.tracef("failed sending to: %s", owner).collectException; 235 } 236 } 237 }